//
// file : inet_tmr.c
// date : 09/09/07
//
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <malloc.h>
#include "inet_ipc.h"
#include "inet_tmr.h"

//
// allocate free nodes and initialize master timer
// passing 'mem' as null and 'count' as zero to get 'size'
// in bytes. pass 'count' as negative to have mem allocated
//
void*   __stdcall tmr_alloca_(void *mem, size_t *size, long count)
{
    long      node_cnt;
    size_t    total;
    uthead_t *thead;
    utnode_t *tnode;

    node_cnt = count;

    if( node_cnt < 0 ) node_cnt *= (-1); // make it positive
    if( node_cnt < 1 ) node_cnt = 1;

    total = sizeof(uthead_t) + sizeof(utnode_t) * ( node_cnt - 1 );

    total = (( total + 15 ) / 16) * 16;  // aligned to 16-byte 

    if( mem == 0 )
    {
       if( size ) *size = total;

       if( count < 0 )
       {
           // we allocate memory
           mem = malloc( total );
       }
    }

    // return if request size or out of memory.
    if( mem == 0 || (size && *size < total) ) return 0;  

    thead = mem;

    memset(mem, 0, total);  // cleanup

    thead->count = node_cnt;  
    //
    for( tnode = thead->array; node_cnt > 0; node_cnt-- )
    {
        // put node into blist
        tnode->next_ = thead->blist;
        thead->blist = tnode;
        tnode++;
    }

    return thead;
}

void*     __stdcall tmr_setownr(void *thead, void *domain)
{
    ((uthead_t*)thead)->owner = domain;

    return thead;
}

//
// get a node from (free) blist for user
//
utnode_t* __stdcall tmr_getnode(void *thead)
{
    utnode_t *tnode;

    assert( thead );

    tnode = ((uthead_t*)thead)->blist;

    if( tnode ) ((uthead_t*)thead)->blist = tnode->next_;

    // intialize it
    memset( tnode, 0, sizeof(*tnode));

    return  tnode;
}

//
// put a timer node to (active) alist
// nodes in alist are ordered in acsending order of expiration times
// the time is a 4-byte value in millisends so will wrap in 49.7 days
// but we expect the neighboring node always have a time interval less
// than half of 49.7 days. the comparison of two timer values is by
// if (unsigned long)(a-b) < (unsigned long)(1<<31) then a > b 
// otherwise a <= b, both a and b are unsigned long.
// a > b means that a comes after b in time
//
#define TICK_GREATER(a, b) ((unsigned long)((a)-(b))< (unsigned long)(1<<31))
#define TICK_NOLESS(a, b)  ((unsigned long)((a)-(b))<=(unsigned long)(1<<31))


//
// put a timer node in to list.
//
void    __stdcall tmr_putnode(void *thead, utnode_t *tnode)
{
    utnode_t *inode, *prev_;

    assert( thead );
    assert( tnode );
        
    // we may want to check validity of tnode as a pointer

    // copy ticks from user set value once
    if( tnode->ticks == 0 ) 
    {
        tnode->cycle = tnode->timer.ut_cycle; // if a periodic timer

        if( tnode->timer.ut_ticks )
        {
            // caller set absolute tick count
            tnode->ticks = tnode->timer.ut_ticks;
            tnode->delay = tnode->ticks - tick_count();   // relative
        }
        else if( tnode->ticks == 0 && tnode->timer.ut_delay >= 0 )
        {
            // caller set relative tick count
            // ut_delay must be positive
            tnode->delay = tnode->timer.ut_delay;
            tnode->ticks = tick_count() + tnode->delay;
        }
        else  // this is wrong; fire asap anyway
        {
           tnode->delay = 0;
           tnode->ticks = tick_count();
        }
    }

    // insert node to alist based on ticks

    inode = ((uthead_t*)thead)->alist;

    if( 0 == inode || TICK_NOLESS(inode->ticks, tnode->ticks) )
    {
        // insert before first in list
        tnode->next_ = ((uthead_t*)thead)->alist;
        ((uthead_t*)thead)->alist = tnode;

        // activate it
        if( ((uthead_t*)thead)->beat_ == 0 ) tmr_trigger( thead, tnode->delay );
 
        return;
    }

    // search for the place for tnode
    tnode->next_ = 0;

    prev_ = inode;
    inode = inode->next_;

    for( ; inode; prev_ = inode, inode = inode->next_ )
    {
        if( TICK_NOLESS(inode->ticks, tnode->ticks) )
        {
            // insert tnode before indoe
            tnode->next_ = inode;
            break;
        }
    }

    // attach tnode to prev_
    prev_->next_ = tnode;
}


//
// walk thru alist and call timer routine for those expired.
//
void    __stdcall tmr_signals(void *thead)
{
    utnode_t *inode, *next_;
    //
    // NOTE: Waitable timer may fire before tick count is reached
    //       allow 4 milliseconds inaccuracy (2 ms seen)
    //       this error may cumulate.
    //
    unsigned long now_ = tick_count()+1; 

    if( 0 == (inode = ((uthead_t*)thead)->alist) ) return;

    if( TICK_GREATER( inode->ticks, now_) ) return;

    // inode expired for sure, 
    next_ = inode->next_;

    // pick up all expired timers, 
    while( next_ && TICK_NOLESS( now_, next_->ticks ) ) next_ = next_->next_;

    // set new alist
    ((uthead_t*)thead)->alist = next_;

    // set next master timer
    if( next_ && ( ((uthead_t*)thead)->beat_ == 0 ) )
    {
        // relative milliseconds until next expiration
        tmr_trigger( thead, (long)( next_->ticks - tick_count()) ); 
    }

    // handle expired timers
    // this step be placed after all firings are done to avoid infinite loop
    for( ; inode && (inode != ((uthead_t*)thead)->alist); inode = next_ )
    {
        next_ = inode->next_;

        // call timer handler
        if( inode->timer.ut_awake )
        {
            inode->timer.ut_awake(&inode->timer);

            // treating inode->cycle as 'long' instead of 'unsigned long'
            // ensures correct ordering
            if( inode->cycle > 0 ) // maximum cycle is (1<<31 - 1)
            {
                inode->delay = (unsigned long)inode->cycle;
                inode->ticks = tick_count() + inode->delay;

                // insert inode to alist 
                tmr_putnode( thead, inode );

                inode = 0;  // invalidate it
            }
        }

        // return to blist
        if( inode )
        {
            inode->next_ = ((uthead_t*)thead)->blist;

            ((uthead_t*)thead)->blist = inode;
        }
    }
}

//
// start timer heart beats
//
long  __stdcall tmr_beating(void *thead, long ms_beat )
{
    if( ms_beat <= 0 )
    {
        ms_beat = TMR_DEFAULT_BEAT;
    }

    ((uthead_t*)thead)->beat_ = ms_beat;

    tmr_trigger( thead, 0 );

    return ms_beat;
}

//
//  start a timer with a handler - must be in-thread call
//
long  __stdcall tmr_insert_(void *thead, utimer_t *tmrctx )
{
    utnode_t   *tnode;

    assert( tmrctx);

    tnode = tmr_getnode( thead );

    assert( tnode );

    tnode->timer = *tmrctx;

    // must set to zero if using ut_delay
    if( tnode->timer.ut_delay > 0 ) tnode->timer.ut_ticks = 0; 

    tmr_putnode( thead, tnode );

    return 0;
}


//
//  stop all timers - must be in-thread call
//
long  __stdcall tmr_stopall(void *thead )
{
    utnode_t *inode, *next_;

    // handle expired timers
    // this step be placed after all firings are done to avoid infinite loop
    for( inode = ((uthead_t*)thead)->alist; inode ; inode = next_ )
    {
        next_ = inode->next_;

        // call timer handler
        inode->timer.ut_awake = 0;

        inode->next_ = ((uthead_t*)thead)->blist;

        ((uthead_t*)thead)->blist = inode;

    }

    return 0;
}
